/**
 * 
 */
package cn.springsoter.core.tool.config;

import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.OS;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.springsoter.core.tool.annotation.SsLog;
import cn.springsoter.core.tool.constant.SpringSoterConstant;
import cn.springsoter.core.tool.model.entity.SysLog;
import cn.springsoter.core.tool.spi.ISysLogService;
import cn.springsoter.core.tool.utils.JacksonUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;

/**
 * 日志切面配置
 * 
 * @author kevin
 *
 */
@Slf4j
@Component
@Aspect
@ConditionalOnProperty(name = "springsoter.sysLog.enabled", havingValue = "true") // 当配置文件中springsoter.sysLog.enabled=true时生效
public class LogConfig {
	
	@Autowired
	private HttpServletRequest request;
	@Autowired
	private ISysLogService sysLogService;
	
	private final static ThreadLocal<Long> timeHolder = new ThreadLocal<>();
    
    /**
     * 定义切入点：自定义注解的存放位置，如：cn.springsoter.core.tool.annotation.SsLog
     */
    @Pointcut("@annotation(cn.springsoter.core.tool.annotation.SsLog)")
    public void pointcut() {}
    
    /**
     * 方法执行前：设置开始时间
     * 
     * @param joinPoint
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
    	timeHolder.set(System.currentTimeMillis());
    }
    
    
    /**
     * 处理完请求后执行此处代码
     * 
     * @param joinPoint
     * @param controllerLog
     * @param jsonResult
     */
    @AfterReturning(pointcut = "@annotation(sslog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, SsLog sslog, Object jsonResult) {
        handleLog(joinPoint, sslog, null, jsonResult);
    }

    /**
     * 如果处理请求时出现异常，在抛出异常后执行此处代码
     * 
     * @param joinPoint
     * @param controllerLog
     * @param e
     */
    @AfterThrowing(value = "@annotation(sslog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, SsLog sslog, Exception e) {
        handleLog(joinPoint, sslog, e, null);
    }

    /**
     * 日志处理
     */
    private void handleLog(final JoinPoint joinPoint, SsLog sslog, final Exception e, Object jsonResult) {
        try {
        	SysLog sysLog = new SysLog();
            // *========数据库日志=========*//
        	// 设置请求状态：1-成功，0-失败/异常
        	sysLog.setStatus(SpringSoterConstant.ONE);
        	
        	//设置耗时
        	sysLog.setCostTime(Long.valueOf(System.currentTimeMillis() - timeHolder.get()).intValue());
        	
        	// 请求的地址
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert requestAttributes != null;
            HttpServletRequest request = requestAttributes.getRequest();
            sysLog.setOperUrl(request.getRequestURI());
            
            // 设置异常信息
            if (e != null) {
                sysLog.setStatus(SpringSoterConstant.ZERO);
                sysLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            sysLog.setMethod(className + "." + methodName + "()");
            
            // 设置请求方式
            sysLog.setRequestMethod(request.getMethod());
            
            // 设置IP地址
            sysLog.setOperIp(ServletUtil.getClientIP(request, null));
            
            // 设置客户端系统OS
            String ua = request.getHeader("User-Agent");
    		UserAgent userAgent = UserAgentUtil.parse(ua);
    		if(userAgent != null) {
    			log.debug("***********User-Agent***********>"+ua);
    			OS os = userAgent.getOs();
    			if(os != null) {
    				sysLog.setClientOs(os.getName());
    			}
    		}
            
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, sslog, sysLog, jsonResult, request);
            
            // 设置当前时间
            sysLog.setOperTime(new Date());
            
            // 保存日志
//            final ServiceLoader<ISysLogService> loader = ServiceLoader.load(ISysLogService.class);
//            for (ISysLogService sysLogService : loader) {
//            	sysLogService.saveSysLog(sysLog);
//            }
            sysLogService.saveSysLog(sysLog);
        } catch (Exception ex) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", ex.getMessage());
            ex.printStackTrace();
        } finally {
			timeHolder.remove();
		}
    }
    
    /**
     * 获取注解信息
     *  
     * @param joinPoint
     * @param log
     * @param sysLog
     * @param jsonResult
     * @param request
     * @throws Exception
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, SsLog sslog, SysLog sysLog, Object jsonResult, HttpServletRequest request) throws Exception {
    	// 设置action动作
    	sysLog.setBusinessType(sslog.businessType().name());
    	
    	// 设置标题：如果没设置title，并且如果swagger有注解，则取swagger的注解
    	if(StringUtils.isBlank(sslog.title())) {
    		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Operation swaggerOperation = methodSignature.getMethod().getAnnotation(Operation.class);
            if(Objects.nonNull(swaggerOperation) && StringUtils.isNotBlank(swaggerOperation.summary())) {
            	sysLog.setTitle(swaggerOperation.summary());
            }
    	} else {
    		sysLog.setTitle(sslog.title());
    	}
    	
        // 设置方法参数
        setRequestValue(joinPoint, sysLog, request);
    }

    /**
     * 设置请求值
     * 
     * @param joinPoint
     * @param sysLog
     * @param request
     * @throws Exception
     */
    private void setRequestValue(JoinPoint joinPoint, SysLog sysLog, HttpServletRequest request) throws Exception {
        String requestMethod = request.getMethod();
        if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            sysLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
        	// 批量获取@PathVariable的值
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            if(paramsMap != null && paramsMap.size() > 0) {
            	sysLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
            } else {
            	// 如果没有PathVariable的值，则获取传递的方法参数值
            	String params = argsArrayToString(joinPoint.getArgs());
            	sysLog.setOperParam(StringUtils.substring(params, 0, 2000));
            }
        }
    }
    
    /**
     * 解析方法参数信息
     * 
     * @param paramsArray
     * @return
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        params.append(JacksonUtil.toJson(o)).append(" ");
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }
    
    /**
     * 判断对象类型
     * 
     * @param o
     * @return
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}
